#include <logd.h>

static void child_handler(int signum) {
    switch(signum) {
		case SIGALRM: exit(EXIT_FAILURE); break;
		case SIGUSR1: exit(EXIT_SUCCESS); break;
		case SIGCHLD: exit(EXIT_FAILURE); break;
    }
}

static void finalizer(void) {
	if (!msgctl(msgQID, IPC_RMID, 0)){
		syslog(LOG_NOTICE, "MQ %i removed",msgQID);
	} else {
		syslog(LOG_ERR, "error remove MQ %i, code=%d (%s)",msgQID, errno, strerror(errno));
	}
	if (!lockf(f_lock, F_ULOCK,0)) {
		syslog(LOG_NOTICE, "pid-file unlocked" );
	} else {
		syslog(LOG_ERR, "error unlock pid-file, code=%d (%s)", errno, strerror(errno));
	}
	if(!remove(config.lockfile_path ) ) {
		syslog(LOG_NOTICE, "lock-file removed");
	} else {
		syslog(LOG_ERR, "error remove pid-file, code=%d (%s)", errno, strerror(errno));
	}
	syslog(LOG_NOTICE, "terminated");
    closelog();
	exit(EXIT_SUCCESS);
}

static void signal_dump(int signum, siginfo_t *info, void *ptr) {
    syslog( LOG_INFO, "SIGUSR2 received, log file recreated");
	setLogname();
}

static void signal_exit(int signum, siginfo_t *info, void *ptr) {
    syslog( LOG_INFO, "signal exit received %i", signum);
	finalizer();
}

static void signal_error(int signum, siginfo_t *info, void *ptr) {
    void* ErrorAddr;
    void* Trace[16];
    int    x;
    int    TraceSize;
    char** Messages;
	/* write signal to log */
    syslog(LOG_INFO,"Got signal: %s, Addr: %X\n", strsignal(signum), (unsigned int)info->si_addr);
	/* detect x32 or x64*/
    #if __WORDSIZE == 64
        ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_RIP];
    #else 
        ErrorAddr = (void*)((ucontext_t*)ptr)->uc_mcontext.gregs[REG_EIP];
    #endif

    /*Get backtrace*/
    TraceSize = backtrace(Trace, 16);
    Trace[1] = ErrorAddr;

    /*decode trace*/
    Messages = backtrace_symbols(Trace, TraceSize);
    if (Messages)
    {
        syslog(LOG_INFO,"== Backtrace ==\n");
        for (x = 1; x < TraceSize; x++)
        {
            syslog(LOG_INFO,"%s\n", Messages[x]);
        }
        syslog(LOG_INFO,"== End Backtrace ==\n");
        free(Messages);
    }
    syslog(LOG_INFO,"daemon will be stopped, try to finalize\n");
    finalizer();    
    exit(EXIT_FAILURE);
}

static void createPidFile(pid_t pid) {
    char buf[10];
	/*try to open pid file*/
	f_lock = open(config.lockfile_path, O_RDWR|O_CREAT|O_EXCL,0640);
	/*this file is exist*/
    if (f_lock == -1) {
		syslog( LOG_INFO, "unable to create pid-file %s, code=%d (%s)",
                    config.lockfile_path, errno, strerror(errno) );
        f_lock = open(config.lockfile_path, O_RDWR,0640);
        if (f_lock>0) {
			syslog( LOG_INFO, "will try to use previous pid-file");
		} else {
			syslog( LOG_ERR, "problem with existing pid-file, exiting");
			exit(EXIT_FAILURE);
		}
    } else {
		syslog( LOG_INFO, "new lock-file created %s", config.lockfile_path);
	}
	/*lock pid file*/
	if (lockf(f_lock,F_TEST,0)<0) {
		syslog( LOG_ERR, "unable to lock lock-file, code=%d (%s), looks like another daemon is running",
                   errno, strerror(errno) );
		exit(EXIT_FAILURE);
	} else {
		if (lockf(f_lock,F_TLOCK,0)==0) {
			syslog( LOG_INFO, "lock-file locked");
		} else {
			syslog( LOG_ERR, "unable to lock lock-file, code=%d (%s)",
                   errno, strerror(errno) );
                   exit(EXIT_FAILURE);
		}
	}
	/*write pid to pid file*/
    snprintf(buf, 10, "%ld", (long)pid);
    if (write(f_lock, buf, strlen(buf)) != strlen(buf)) {
        syslog( LOG_ERR, "unable to write pid-file %s, code=%d (%s)",
                    config.lockfile_path, errno, strerror(errno) );
        
        finalizer();
    } else {
		syslog( LOG_INFO, "PID %s writed to pid-file %s", buf,
                    config.lockfile_path);
	}
}

static void daemonize() {
    pid_t pid, sid, parent, child;
	
    /* already a daemon */
    if ( getppid() == 1 ) return;

    /* Drop user if there is one, and we were run as root */
    if ( getuid() == 0 || geteuid() == 0 ) {
        struct passwd *pw = getpwnam(RUN_AS_USER);
        if ( pw ) {
            syslog( LOG_NOTICE, "setting user to " RUN_AS_USER );
            setuid( pw->pw_uid );
        }
    }

    /* Trap signals that we expect to recieve */
    signal(SIGCHLD,child_handler);
    signal(SIGUSR1,child_handler);
    signal(SIGALRM,child_handler);

    /* Fork off the parent process */
    pid = fork();
    if (pid < 0) {
        syslog( LOG_ERR, "unable to fork daemon, code=%d (%s)",
                errno, strerror(errno) );
        exit(EXIT_FAILURE);
    }
	if (pid == 0) {
		/*set signal handlers*/
		
		/*for errors*/ 
        sa.sa_handler = signal_error;
        sa.sa_flags = SA_SIGINFO;
        sigemptyset(&sa.sa_mask);
        sigaction(SIGFPE, &sa, 0); 
        sigaction(SIGILL, &sa, 0); 
        sigaction(SIGSEGV, &sa, 0); 
        sigaction(SIGBUS, &sa, 0); 
        	
		/*for exit*/
        sa.sa_handler = signal_exit;
        sigaction(SIGHUP, &sa, 0);
        sigaction(SIGINT, &sa, 0);
        sigaction(SIGQUIT, &sa, 0);
        sigaction(SIGABRT, &sa, 0);
        sigaction(SIGTERM, &sa, 0);
        
        /*for dump*/
        sa.sa_handler = signal_dump;
        sigaction(SIGUSR2, &sa, 0);
	}
    /* If we got a good PID, then we can exit the parent process. */
    if (pid > 0) {
        /* Wait for confirmation from the child via SIGTERM or SIGCHLD, or
           for two seconds to elapse (SIGALRM).  pause() should not return. */
		syslog(LOG_INFO, "successful forked daemon with pid %i",pid);
        alarm(2);
        pause();
        exit(EXIT_SUCCESS);
    }

    /* At this point we are executing as the child process */
    parent = getppid();
    child = getpid();
    createPidFile(child);
	syslog(LOG_INFO,"get parent pid %i", parent);
	
        /* Change the file mode mask */
    umask(0);

    /* Create a new SID for the child process */
    sid = setsid();
    if (sid < 0) {
        syslog( LOG_ERR, "unable to create a new session, code %d (%s)",
                errno, strerror(errno) );
        exit(EXIT_FAILURE);
    }

    /* Change the current working directory.  This prevents the current
       directory from being locked; hence not being able to remove it. */
    if ((chdir("/")) < 0) {
        syslog( LOG_ERR, "unable to change directory to %s, code %d (%s)",
                "/", errno, strerror(errno) );
        exit(EXIT_FAILURE);
    }

    /* Redirect standard files to /dev/null */
    freopen( "/dev/null", "r", stdin);
    freopen( "/dev/null", "w", stdout);
    freopen( "/dev/null", "w", stderr);

    /* Tell the parent process that all ok */
    kill(parent,SIGUSR1);
}

void setLogname(){
	time_t timenow = time(NULL);
	sprintf(logfile, "%s/%s-%ld.log",config.logfile_path,config.general_servername,timenow);
	syslog(LOG_INFO, "using log-file %s for writing",logfile);
} 

void prepare_mq(void) {
	struct msqid_ds buf;
	int rc;

	//get MQ QID
	msgQID = msgget(config.mq_key, (IPC_CREAT|0666));
	if (msgQID<0) {
		syslog(LOG_ERR, "error message queue GET with key %d, code %d (%s)", config.mq_key, errno, strerror(errno));
		finalizer();
	}
	//get MQ stats
	rc = msgctl(msgQID,IPC_STAT,&buf);
	if (rc<0) {
		syslog(LOG_ERR, "error message queue CONTROL with key %d, code %d (%s)", config.mq_key, errno, strerror(errno));
		finalizer();
	}
	syslog(LOG_INFO,"got queue %i, queue has %i messages and %i system size", msgQID,(int)buf.msg_qnum, (int)buf.msg_qbytes);
	//try to set MQ size to defined value
	if ((buf.msg_qbytes < config.mq_syssize) && (config.mq_syssize > 0) && (config.mq_syssize <= USHRT_MAX)){
		syslog(LOG_INFO,"try to alter queue %i with size %i", msgQID,(ushort)config.mq_syssize);
		buf.msg_qbytes = (ushort)config.mq_syssize;
		rc = msgctl(msgQID,IPC_SET,&buf);
		if (rc<0) {
			syslog(LOG_ERR, "error message queue CONTROL with key %d, code %d (%s)", config.mq_key, errno, strerror(errno));
			finalizer();
		}
		syslog(LOG_INFO,"got queue %i, queue has %i messages and %i system size", msgQID,(int)buf.msg_qnum, (int)buf.msg_qbytes);
	}
	syslog(LOG_INFO, "message queue %i setup ok with key %d",msgQID, config.mq_key);
}

int writemsg(const char *msg, int len) {
	char message[8192];
	char buf[8192];
	/*free buffers*/
	memset(message, 0, sizeof(message)); 
	memset(buf, 0, sizeof(buf)); 
	/*copy received message to buffer*/
	strncpy(buf, msg, len);
	/*force set null-term char*/
	buf[len+1] = '\0';
	//syslog(LOG_INFO, "got message %s with size %i and overall size %i",buf, strlen(buf), sizeofmessage);
	/*  */
	snprintf(message, sizeof(message), "%s;%s\r\n", config.general_servername, buf);
	return write(f_log, message, strlen(message));
}
void work_mq(void) {
	int message_len, bytes_writed = 0;
    Message recvMsg;
	struct stat log_stat;

	/*get message queue*/
	prepare_mq();	
	/*prepare filename as unix timestamp*/
	setLogname();
	
	while(1)/*main loop*/
	{
		/*get message from msqQID*/
		message_len = msgrcv(msgQID, &recvMsg, config.mq_msgsize, 0, (IPC_CREAT | IPC_EXCL | 0666));
		if (message_len > 0)
		{
			f_log=open(logfile,O_RDWR|O_CREAT|O_APPEND,config.logfile_perm);
			if (f_log>0) {
				/*get file info*/
				fstat(f_log, &log_stat);
				/*if size threshold reached - rename work file*/
				if (log_stat.st_size >= config.logfile_maxsize*1024*1024) {
					if (!strcmp(config.logfile_behavior, "roll")){
						syslog(LOG_INFO, "log file %s have reached max size",logfile);
						setLogname();
						syslog(LOG_INFO, "log file %s will be used", logfile);
						bytes_writed = writemsg(recvMsg.text, message_len);
					} else { 
						/*if not "roll" - skip writing*/ 
					}
				} else {
					bytes_writed = writemsg(recvMsg.text, message_len);
				}
				close(f_log);
				if (bytes_writed<0) {
						syslog( LOG_ERR, "error writing log file %s, code %d (%s)",logfile, errno, strerror(errno));
						finalizer();
				} 
			} else {
				syslog( LOG_ERR, "error opening log file %s, code %d (%s)",logfile, errno, strerror(errno));
				finalizer();
			}
		} 
	}
}

static int config_handler(void* user, const char* section, const char* name,
                   const char* value) {
    configuration* pconfig = (configuration*)user;

    #define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
    if (MATCH("lockfile", "path")) {
        pconfig->lockfile_path = strdup(value);
    } else if (MATCH("mq", "key")) {
        pconfig->mq_key = atoi(value);
    } else if (MATCH("general", "servername")) {
        pconfig->general_servername = strdup(value);
    } else if (MATCH("mq", "msgsize")) {
        pconfig->mq_msgsize = atoi(value);
    } else if (MATCH("mq", "syssize")) {
        pconfig->mq_syssize = atoi(value);
    } else if (MATCH("logfile", "behavior")) {
        pconfig->logfile_behavior = strdup(value);
    } else if (MATCH("logfile", "perm")) {
        pconfig->logfile_perm = strtoul(value, NULL, 8);
    } else if (MATCH("logfile", "path")) {
        pconfig->logfile_path = strdup(value);
    } else if (MATCH("logfile", "maxsize")) {
        pconfig->logfile_maxsize = atoi(value);
    } else if (MATCH("db", "host")) {
        pconfig->db_host = strdup(value);
    } else if (MATCH("db", "port")) {
        pconfig->db_port = atoi(value);
    } else if (MATCH("db", "host")) {
        pconfig->db_name = strdup(value);
    } else if (MATCH("db", "user")) {
        pconfig->db_user = strdup(value);
    } else if (MATCH("db", "pass")) {
        pconfig->db_pass = strdup(value);
    } else {
        return 0;  /* unknown section/name, error */
    }
    return 1;
}

int main(int argc, char *argv[]) {
	int rc;
	//check if we run as root
	int user = getuid();
	if (user) {
		printf("Logger daemon should be run as root\n");
		exit(EXIT_SUCCESS);
	}
    /* Initialize the logging interface */
    openlog(DAEMON_NAME,LOG_PID,LOG_LOCAL5);
    syslog(LOG_INFO,"starting");
    /* Process command line arguments */
	if (argc==2) {
		rc=ini_parse(argv[1], config_handler, &config);
		if(rc < 0){
			printf("Error reading config file %s on line %i", argv[1], rc);
			return -1;
		} else {
			syslog(LOG_INFO,"Config file %s readed", argv[1]);
		}
	} else {
		printf("Argument parameter missing, should be ./logd <path to config file>\n");
		exit(EXIT_FAILURE);
	}
    /* Daemonize */
    daemonize(); 
    /* Now we are a daemon -- do the work for which we were paid */
    work_mq();
	finalizer();
    exit(EXIT_SUCCESS);
}
